﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace SharpMedia
{
    /// <summary>
    /// A basic utility class for all-purpose functionality.
    /// </summary>
    public static partial class Util
    {
        public static void RunAsync<T, V, X, Y>(Action<T, V, X, Y> function, T a, V b, X c, Y d)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunAction), new ActionJob<T, V, X, Y>(function, a, b, c, d));
        }
        public static void RunAsync<T, V, X>(Action<T, V, X> function, T a, V b, X c)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunAction), new ActionJob<T, V, X>(function, a, b, c));
        }
        public static void RunAsync<T, V>(Action<T, V> function, T a, V b)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunAction), new ActionJob<T, V>(function, a, b));
        }
        public static void RunAsync<T>(Action<T> function, T a)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunAction), new ActionJob<T>(function, a));
        }
        public static void RunAsync(Action function)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunAction), new ActionJob(function));
        }

        private static void RunAction(object obj)
        {
            ActionJob job = obj as ActionJob;
            job.Execute();
        }

        public static void RunAsync<T>(Func<T> function, Action<T> callBack)
        {
            FuncJob<T> job = new FuncJob<T>(function, callBack);
            function.BeginInvoke(new AsyncCallback(job.End), job);
        }

        public static void RunAsync<T,V>(Func<V, T> function, Action<T> callBack, V a)
        {
            FuncJob<V,T> job = new FuncJob<V, T>(function, callBack);
            function.BeginInvoke(a, new AsyncCallback(job.End), job);
        }

    }

    public class ExecutionQueue
    {
        private Queue<ActionJob> syncQueue = new Queue<ActionJob>();
        public bool Running { get; private set; }

        public ExecutionQueue()
        {
            Running = false;
        }


        public void Enqueue<T, V, X, Y>(Action<T, V, X, Y> function, T a, V b, X c, Y d)
        {

            lock (syncQueue)
            {
                syncQueue.Enqueue(new ActionJob<T, V, X, Y>(function, a, b, c, d));
                Monitor.PulseAll(syncQueue);
            }
        }
        public void Enqueue<T, V, X>(Action<T, V, X> function, T a, V b, X c)
        {

            lock (syncQueue)
            {
                syncQueue.Enqueue(new ActionJob<T, V, X>(function, a, b, c));
                Monitor.PulseAll(syncQueue);
            }
        }
        public void Enqueue<T, V>(Action<T, V> function, T a, V b)
        {

            lock (syncQueue)
            {
                syncQueue.Enqueue(new ActionJob<T, V>(function, a, b));
                Monitor.PulseAll(syncQueue);
            }
        }
        public void Enqueue<T>(Action<T> function, T a)
        {

            lock (syncQueue)
            {
                syncQueue.Enqueue(new ActionJob<T>(function, a));
                Monitor.PulseAll(syncQueue);
            }
        }
        public void Enqueue(Action function)
        {
            lock (syncQueue)
            {
                syncQueue.Enqueue(new ActionJob(function));
                Monitor.PulseAll(syncQueue);
            }
        }
        
        
        public void ExecuteQueue()
        {
            do
            {
                Queue<ActionJob> currentMethods = new Queue<ActionJob>();

                // Locks SyncRun Queue and gets all calls in line.
                lock (syncQueue)
                {
                    while (Running && syncQueue.Count == 0)
                        Monitor.Wait(syncQueue);

                    while (syncQueue.Count > 0)
                    {
                        currentMethods.Enqueue(syncQueue.Dequeue());
                    }
                }
                // calls all methods waiting
                while (currentMethods.Count > 0)
                {
                    ActionJob job = currentMethods.Dequeue();
                    job.Execute();
                }
            
            } while (Running);
        }

        public void ExecuteQueueAsync()
        {
            Running = true;
            Util.RunAsync(ExecuteQueue);
        }

        public void Stop()
        {
            lock (syncQueue)
            {
                Running = false;
                Monitor.PulseAll(syncQueue);
            }
        }

    }


    public class FuncJob<T>
    {
        public Func<T> Func { get; protected set; }
        public Action<T> Callback { get; protected set; }
        public FuncJob(Func<T> func, Action<T> callback)
        {
            this.Func = func;
            this.Callback = callback;
        }

        public void End(IAsyncResult result)
        {
            FuncJob<T> job = (FuncJob<T>)result.AsyncState;
            
            T a = job.Func.EndInvoke(result);
            
            job.Callback(a);
        }
    }

    public class FuncJob<V,T>
    {
        public Func<V,T> Func { get; protected set; }
        public Action<T> Callback { get; protected set; }
        public FuncJob(Func<V,T> func, Action<T> callback) 
        {
            this.Func = func;
            this.Callback = callback;
        }

        public void End(IAsyncResult result)
        {
            FuncJob<V,T> job = (FuncJob<V,T>)result.AsyncState;

            T a = job.Func.EndInvoke(result);

            job.Callback(a);
        }
    }

    public class ActionJob
    {
        public Delegate Function { get; protected set; }
        public ActionJob(Action action)
        {
            this.Function = action;
        }

        public virtual void Execute()
        {
            ((Action)Function)();
        }
    }

    public class ActionJob<A> : ActionJob
    {
        public A Arg1 { get; set; }
        public ActionJob(Action<A> action, A a)
            : base(null)
        {
            this.Arg1 = a;
            this.Function = action;
        }
        public override void Execute()
        {
            ((Action<A>)Function)(Arg1);
        }
    }

    public class ActionJob<A, B> : ActionJob<A>
    {
        public B Arg2 { get; set; }
        public ActionJob(Action<A, B> action, A a, B b)
            : base(null, a)
        {
            this.Arg2 = b;
            this.Function = action;
        }
        public override void Execute()
        {
            ((Action<A, B>)Function)(Arg1, Arg2);
        }

    }
    public class ActionJob<A, B, C> : ActionJob<A, B>
    {
        public C Arg3 { get; set; }
        public ActionJob(Action<A, B, C> action, A a, B b, C c)
            : base(null, a, b)
        {
            this.Function = action;
            this.Arg3 = c;
        }
        public override void Execute()
        {
            ((Action<A, B, C>)Function)(Arg1, Arg2, Arg3);
        }
    }
    public class ActionJob<A, B, C, D> : ActionJob<A, B, C>
    {
        public D Arg4 { get; set; }
        public ActionJob(Action<A, B, C, D> action, A a, B b, C c, D d)
            : base(null, a, b, c)
        {
            this.Function = action;
            this.Arg4 = d;
        }
        public override void Execute()
        {
            ((Action<A, B, C, D>)Function)(Arg1, Arg2, Arg3, Arg4);
        }
    }
}
